package mk.jug.academy.dao;

import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import mk.jug.academy.model.BaseEntity;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;


/**
 * Generic DAO to be extended by all underlying
 * DAO objects.
 *  
 * @author Ice.Penov
 */
@Repository @Transactional
public class GenericDAO<T extends BaseEntity> {
  
  private static final EntityManagerFactory INSTANCE = Persistence.createEntityManagerFactory("transactions-optional");
  
  /**
   * Creates an entity manager.
   */
  protected EntityManager createEntityManager() {
    return INSTANCE.createEntityManager();
  }
  
  /**
   * Provides a uniform way of providing and handling
   * the persistence context in a GAE environment.
   *
   * @author Ice.Penov
   */
  protected abstract class QueryEMContext<K> {
	  private K result;
	  
	  public abstract K query(EntityManager em);
	  
	  public QueryEMContext() {
		  EntityManager em = createEntityManager();
		  try {
			  result = query(em);
		  } finally {
			  releaseEntityManager(em);
		  }
	  }
	  
	  public K result() {
		  return result;
	  }	  
  }
  
  /**
   * Provides a uniform way of providing and handling
   * the persistence context in a GAE environment.
   *
   * @author Ice.Penov
   */  
  protected abstract class UpdateEMContext {
	  
	  public abstract void update(EntityManager em);
	  
	  public UpdateEMContext() {
		  EntityManager em = createEntityManager();
		  try {
			  update(em);
		  } finally {
			  releaseEntityManager(em);
		  }
	  }	  
  }
  
  
  /**
   * Releases the entity manager.
   * 
   * @param em the entity manager to be released
   */
  protected void releaseEntityManager(EntityManager em) {
    if (em != null) {      
      em.close();
    }
  }
  
  /**
   * Saves (persists or updates) the passed object
   * into the persistence provider.
   * 
   * @param object the object subject to the save operation
   * @return the saved object
   */
  public <K extends T> K save(K object) {
    EntityManager em = createEntityManager();
    try {
    	object.setModified(new Date());
      if (object.getId() == null) {
        em.persist(object);
        return object;
      } else {
        return em.merge(object);
      }      
    } finally {
      releaseEntityManager(em);
    }
  }
  
  /**
   * Finds the specified entity.
   * 
   * @param clazz the class of the entity
   * @param objectId the unique ID of the entity
   * @return the entity
   */
  public <K extends T> K find(Class<K> clazz, final Long objectId) {
    if (objectId == null) {
      throw new IllegalArgumentException("Input must not be empty");
    }
    EntityManager em = createEntityManager();
    try {
      final K object = em.find(clazz, objectId);
      return object;
    } finally {
      releaseEntityManager(em);
    }
  }
    
  //until this is resolved for JPA: http://code.google.com/p/datanucleus-appengine/issues/detail?id=24
  @SuppressWarnings("rawtypes")
  protected List initialize(List list) {
	  list.size();
	  return list;
  }
  
}